1 /**
2  * Copyright (c) 2019 Laurent CARON.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  * Laurent CARON (laurent.caron at gmail dot com) - initial API and implementation (bug 542777)
13  */
14 package org.eclipse.swt.custom;
15 
16 import org.eclipse.swt.*;
17 import org.eclipse.swt.graphics.*;
18 import org.eclipse.swt.widgets.*;
19 
20 /**
21  * This class add the following behaviour to <code>StyledText</code> widgets:
22  * <p>
23  * When the user clicks on the wheel, a circle with arrows appears. When the user moves the mouse,
24  * the StyledText content is scrolled (on the right or on the left for horizontal movements, up or down for vertical movements).
25  * </p>
26  *
27  * @since 3.110
28  */
29 class MouseNavigator {
30 	private final StyledText parent;
31 	boolean navigationActivated = false;
32 	private GC gc;
33 	private static final int CIRCLE_RADIUS = 15;
34 	private static final int CENTRAL_POINT_RADIUS = 2;
35 	private Point originalMouseLocation;
36 	private final Listener mouseDownListener, mouseUpListener, paintListener, mouseMoveListener, focusOutListener;
37 	private boolean hasHBar, hasVBar;
38 	private Cursor previousCursor;
39 
MouseNavigator(final StyledText styledText)40 	MouseNavigator(final StyledText styledText) {
41 		if (styledText == null) {
42 			SWT.error(SWT.ERROR_NULL_ARGUMENT);
43 		}
44 		if (styledText.isDisposed()) {
45 			SWT.error(SWT.ERROR_WIDGET_DISPOSED);
46 		}
47 		parent = styledText;
48 
49 		mouseDownListener = (event) -> {
50 			onMouseDown(event);
51 		};
52 		parent.addListener(SWT.MouseDown, mouseDownListener);
53 
54 		mouseUpListener = (event) -> {
55 			onMouseUp(event);
56 		};
57 		parent.addListener(SWT.MouseUp, mouseUpListener);
58 
59 		paintListener = (event) -> {
60 			onPaint(event);
61 		};
62 		parent.addListener(SWT.Paint, paintListener);
63 
64 		mouseMoveListener = (event) -> {
65 			onMouseMove(event);
66 		};
67 		parent.addListener(SWT.MouseMove, mouseMoveListener);
68 
69 		focusOutListener = (event) -> {
70 			onFocusOut(event);
71 		};
72 		parent.addListener(SWT.FocusOut, focusOutListener);
73 	}
74 
onMouseDown(Event e)75 	void onMouseDown(Event e) {
76 		if ((e.button != 2) || navigationActivated) {
77 			return;
78 		}
79 
80 		if (!parent.isVisible() || !parent.getEnabled() || parent.middleClickPressed) {
81 			return;
82 		}
83 
84 		// Widget has no bar or bars are not enabled
85 		initBarState();
86 
87 		if (!hasHBar && !hasVBar) {
88 			return;
89 		}
90 
91 		navigationActivated = true;
92 		previousCursor = parent.getCursor();
93 		parent.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_ARROW));
94 		originalMouseLocation = getMouseLocation();
95 		parent.redraw();
96 	}
97 
initBarState()98 	private void initBarState() {
99 		hasHBar = computeHasHorizontalBar();
100 		hasVBar = computeHasVerticalBar();
101 	}
102 
computeHasHorizontalBar()103 	private boolean computeHasHorizontalBar() {
104 		final ScrollBar horizontalBar = parent.getHorizontalBar();
105 		final boolean hasHorizontalBar = horizontalBar != null && horizontalBar.isVisible();
106 		final boolean exceedHorizontalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).x > parent.getSize().x;
107 		return hasHorizontalBar && exceedHorizontalSpace;
108 	}
109 
computeHasVerticalBar()110 	private boolean computeHasVerticalBar() {
111 		final ScrollBar verticalBar = parent.getVerticalBar();
112 		final boolean hasVerticalBar = verticalBar != null && verticalBar.isEnabled();
113 		final boolean exceedVerticalSpace = parent.computeSize(SWT.DEFAULT, SWT.DEFAULT).y > parent.getSize().y;
114 		return hasVerticalBar && exceedVerticalSpace;
115 	}
116 
onMouseUp(Event e)117 	private void onMouseUp(Event e) {
118 		if ((computeDist() < CIRCLE_RADIUS) && (computeDist() >= 0)) {
119 			return;
120 		}
121 		deactivate();
122 	}
123 
computeDist()124 	public int computeDist() {
125 		if (originalMouseLocation == null) {
126 			return -1;
127 		}
128 		final Point mouseLocation = getMouseLocation();
129 		final int deltaX = originalMouseLocation.x - mouseLocation.x;
130 		final int deltaY = originalMouseLocation.y - mouseLocation.y;
131 		final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
132 		return dist;
133 	}
134 
deactivate()135 	private void deactivate() {
136 		parent.setCursor(previousCursor);
137 		navigationActivated = false;
138 		originalMouseLocation = null;
139 		parent.redraw();
140 	}
141 
onFocusOut(Event e)142 	private void onFocusOut(Event e) {
143 		deactivate();
144 	}
145 
onMouseMove(Event e)146 	private void onMouseMove(Event e) {
147 		if (!navigationActivated) {
148 			return;
149 		}
150 
151 		final Point mouseLocation = getMouseLocation();
152 		final int deltaX = originalMouseLocation.x - mouseLocation.x;
153 		final int deltaY = originalMouseLocation.y - mouseLocation.y;
154 		final int dist = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
155 		if (dist < CIRCLE_RADIUS) {
156 			return;
157 		}
158 
159 		parent.setRedraw(false);
160 		if (hasHBar) {
161 			final ScrollBar bar = parent.getHorizontalBar();
162 			bar.setSelection((int) (bar.getSelection() - deltaX * .1));
163 			fireSelectionEvent(e, bar);
164 		}
165 
166 		if (hasVBar) {
167 			final ScrollBar bar = parent.getVerticalBar();
168 			bar.setSelection((int) (bar.getSelection() - deltaY * .1));
169 			fireSelectionEvent(e, bar);
170 		}
171 		parent.setRedraw(true);
172 		parent.redraw();
173 	}
174 
fireSelectionEvent(final Event e, final ScrollBar bar)175 	private void fireSelectionEvent(final Event e, final ScrollBar bar) {
176 		final Event event = new Event();
177 		event.widget = bar;
178 		event.display = parent.getDisplay();
179 		event.type = SWT.Selection;
180 		event.time = e.time;
181 
182 		for (final Listener selectionListener : bar.getListeners(SWT.Selection)) {
183 			selectionListener.handleEvent(event);
184 		}
185 	}
186 
getMouseLocation()187 	private Point getMouseLocation() {
188 		final Point cursorLocation = Display.getCurrent().getCursorLocation();
189 		final Point relativeCursorLocation = parent.toControl(cursorLocation);
190 		return relativeCursorLocation;
191 	}
192 
onPaint(final Event e)193 	private void onPaint(final Event e) {
194 		if (!navigationActivated) {
195 			return;
196 		}
197 
198 		final Rectangle rect = parent.getClientArea();
199 		if (rect.width == 0 || rect.height == 0) {
200 			return;
201 		}
202 		gc = e.gc;
203 		gc.setAntialias(SWT.ON);
204 		gc.setAdvanced(true);
205 
206 		final Color oldForegroundColor = gc.getForeground();
207 		final Color oldBackgroundColor = gc.getBackground();
208 		gc.setBackground(parent.getForeground());
209 
210 		drawCircle();
211 		drawCentralPoint();
212 
213 		drawArrows();
214 
215 		gc.setForeground(oldForegroundColor);
216 		gc.setBackground(oldBackgroundColor);
217 	}
218 
drawCircle()219 	private void drawCircle() {
220 		gc.setBackground(parent.getBackground());
221 		gc.setForeground(parent.getForeground());
222 		gc.setAlpha(200);
223 		gc.fillOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
224 		gc.setBackground(parent.getForeground());
225 		gc.setAlpha(255);
226 		gc.drawOval(originalMouseLocation.x - CIRCLE_RADIUS, originalMouseLocation.y - CIRCLE_RADIUS, CIRCLE_RADIUS * 2, CIRCLE_RADIUS * 2);
227 	}
228 
drawCentralPoint()229 	private void drawCentralPoint() {
230 		gc.fillOval(originalMouseLocation.x - CENTRAL_POINT_RADIUS, originalMouseLocation.y - CENTRAL_POINT_RADIUS, CENTRAL_POINT_RADIUS * 2, CENTRAL_POINT_RADIUS * 2);
231 	}
232 
drawArrows()233 	private void drawArrows() {
234 		gc.setLineWidth(2);
235 		if (hasHBar) {
236 			drawHorizontalArrows();
237 		}
238 		if (hasVBar) {
239 			drawVerticalArrows();
240 		}
241 	}
242 
drawHorizontalArrows()243 	private void drawHorizontalArrows() {
244 		final int[] points = new int[6];
245 		// Left
246 		points[0] = originalMouseLocation.x - 6;
247 		points[1] = originalMouseLocation.y + 3;
248 		points[2] = originalMouseLocation.x - 9;
249 		points[3] = originalMouseLocation.y;
250 		points[4] = originalMouseLocation.x - 6;
251 		points[5] = originalMouseLocation.y - 3;
252 		gc.drawPolyline(points);
253 
254 		// Right
255 		points[0] = originalMouseLocation.x + 7;
256 		points[1] = originalMouseLocation.y + 3;
257 		points[2] = originalMouseLocation.x + 10;
258 		points[3] = originalMouseLocation.y;
259 		points[4] = originalMouseLocation.x + 7;
260 		points[5] = originalMouseLocation.y - 3;
261 		gc.drawPolyline(points);
262 	}
263 
drawVerticalArrows()264 	private void drawVerticalArrows() {
265 		final int[] points = new int[6];
266 		// Upper
267 		points[0] = originalMouseLocation.x - 3;
268 		points[1] = originalMouseLocation.y - 6;
269 		points[2] = originalMouseLocation.x;
270 		points[3] = originalMouseLocation.y - 10;
271 		points[4] = originalMouseLocation.x + 3;
272 		points[5] = originalMouseLocation.y - 6;
273 		gc.drawPolyline(points);
274 
275 		// Lower
276 		points[0] = originalMouseLocation.x - 3;
277 		points[1] = originalMouseLocation.y + 7;
278 		points[2] = originalMouseLocation.x;
279 		points[3] = originalMouseLocation.y + 11;
280 		points[4] = originalMouseLocation.x + 3;
281 		points[5] = originalMouseLocation.y + 7;
282 		gc.drawPolyline(points);
283 
284 	}
285 
dispose()286 	void dispose() {
287 		if (parent.isDisposed()) {
288 			return;
289 		}
290 		parent.removeListener(SWT.MouseDown, mouseDownListener);
291 		parent.removeListener(SWT.MouseUp, mouseUpListener);
292 		parent.removeListener(SWT.Paint, paintListener);
293 		parent.removeListener(SWT.MouseMove, mouseMoveListener);
294 		parent.removeListener(SWT.MouseExit, focusOutListener);
295 	}
296 }
297